package com.htfyun.serialport.uartservo.AX12_Servo;

import com.htfyun.serialport.uartservo.IServoReceivedSerialPacket;
import com.htfyun.serialport.utils.UsefulUtil;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * 格式: header, id, length, error, instruction, parameters, checksum;
 *
 * 具体: FF FF ID LEN ERR INSTRUCTION PARAMETERS CHECKSUM
 * 其中: length 值是: 参数个数(N) + 3,  3是指 err , instruction, checksum
 *
 * CheckSum = ~(ID + Length + Instruction + Parameter1 + ... + Parameter N)
 * 如果计算结果大于 255，则后面字节的值设为校验码的值。
 *
 */

public class AX12_ReceivedSerialPacket implements IServoReceivedSerialPacket {

    private static final byte[] header = new byte[]{(byte) 0xff, (byte) 0xff};

    public static final int SIZE_OF_FIX_PREFIX_DATA = header.length + 2/*id + length*/;

    public static final int LENGTH_OFFSET_FROM_HEADER = 3;

    public static final int MIN_LENGTH = 3;//3是指 err , instruction, checksum
    //
    public static final int PACKAGE_MIN_SIZE = 4 + MIN_LENGTH;//FF FF, id, length, error, instruction, checksum;

    private byte id;
    private byte length;
    private byte instruction;
    private byte error;

    private byte[] parameters;
    private byte checkSum;

    private final boolean isValid;

    public AX12_ReceivedSerialPacket(ByteBuffer byteBuffer) {
        isValid = parseByteBuffer(byteBuffer);
    }

    private boolean parseByteBuffer(ByteBuffer byteBuffer) {

        if (byteBuffer.capacity() < PACKAGE_MIN_SIZE) {
            return false;
        }

        byteBuffer.rewind();
        //小端模式存放
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

        short header = byteBuffer.getShort();

        if (header != getHeaderShort()) {
            return false;
        }

        id = byteBuffer.get();

        length = byteBuffer.get();
        if (length < MIN_LENGTH) {
            return false;
        }

        error = byteBuffer.get();
        instruction = byteBuffer.get();

        parameters = new byte[length - MIN_LENGTH];
        if (parameters.length > 0) {
            byteBuffer.get(parameters, 0, parameters.length);
        }
        checkSum = byteBuffer.get();

        return checkSum();

    }

    private static short getHeaderShort() {
//        return (short) ((header[0] & 0xff) | ((header[1]  << 8) & 0xff00));
        ByteBuffer byteBuffer = getHeaderByteBuffer();
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        return byteBuffer.getShort();
    }

    public static byte[] getHeader() {
        return header;
    }
    public static ByteBuffer getHeaderByteBuffer() {
        return ByteBuffer.wrap(getHeader());
    }

    private boolean checkSum() {
        byte[] parameters = getParameters();

        if (parameters == null) {
            parameters = new byte[0];
        }

        int checkSumInt = (getId() + getLength() + getError() + getInstruction() );
        for (byte para : parameters) {
            checkSumInt += para;
        }

        checkSumInt = ~checkSumInt;

        byte checkSum = (byte) checkSumInt;

        return (checkSum == getCheckSum());
    }


    public byte getId() {
        return id;
    }

    public byte getLength() {
        return length;
    }

    public byte getInstruction() {
        return instruction;
    }

    public byte getError() {
        return error;
    }

    public byte[] getParameters() {
        return parameters;
    }

    public byte getCheckSum() {
        return checkSum;
    }

    @Override
    public boolean isValid() {
        return isValid;
    }

    public byte[] toBytes() {
        int commandLen = header.length + 1/*id*/ + 1/*len*/ + 1/*instrunction*/ + 1 /*error*/ + parameters.length + 1/*checksum*/;
        ByteBuffer byteBuffer = ByteBuffer.allocate(commandLen);
        byteBuffer.put(header);
        byteBuffer.put(getId());
        byteBuffer.put(getLength());
        byteBuffer.put(getError());
        byteBuffer.put(getInstruction());
        byteBuffer.put(parameters);
        byteBuffer.put(getCheckSum());
        return byteBuffer.array();
    }

    public AX12_instructionAttr getInstructionAttr() {

        for (AX12_instructionAttr attr : AX12_instructionAttr.values()) {
            if (attr.getInstruction() == getInstruction()) {
                return attr;
            }
        }
        return AX12_instructionAttr.none;
    }

    public AX12_controlTableAttr getControlTableAttr() {

        //我们这个读写的parameters至少是2个.
        if (parameters == null || parameters.length < 2) {
            return AX12_controlTableAttr.unknown;
        }

        byte address = parameters[0];

        byte byteCount = (byte) (parameters.length - 1);

        for (AX12_controlTableAttr attr : AX12_controlTableAttr.values()) {
            if (address == attr.getAddress() && attr.getByteCount() == byteCount) {
                return attr;
            }
        }

        return AX12_controlTableAttr.unknown;
    }

    /**
     * 由于对AX12的特殊操作, 所以parameter的值一般是1个或者2个byte或者4个byte, 所以转成int后, 都是大于0的
     * //todo 如果超过4个byte, 需要重新写个方法
     * @return
     * 小于0 , 则说明 parameters 不对.
     */
    public Integer getParameterValue() {
        if (parameters == null || parameters.length == 0) {
            return null;
        }
        int startPos = 1;
        int value = 0;
        for (int i = startPos; i < parameters.length && i < 4; i++) {
            value |= (0xff & parameters[i]) << 8 * (i - startPos);
        }
        return value;
    }

    public List<AX12_errorAttr> getErrorAttr() {

        List<AX12_errorAttr> attrList = null;
        for (AX12_errorAttr attr : AX12_errorAttr.values()) {
            if (0 != (attr.getErrorFlag() & getError())) {
                if (attrList == null) {
                    attrList = new ArrayList<>();
                }
                attrList.add(attr);
            }
        }

        return attrList;
    }

    @Override
    public String toString() {
        return UsefulUtil.toHexString(toBytes()) + ", isValid = " + isValid;
    }
}
